package com.nebula.salary.portal.service.impl;

import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.base.Strings;
import com.nebula.salary.common.constants.Const;
import com.nebula.salary.common.constants.ReturnMessage;
import com.nebula.salary.common.exception.CustomException;
import com.nebula.salary.common.exception.ErrorException;
import com.nebula.salary.common.exception.UserPasswordNotMatchException;
import com.nebula.salary.common.result.ServerResponse;
import com.nebula.salary.common.utils.JwtTokenUtil;
import com.nebula.salary.common.utils.RedisUtil;
import com.nebula.salary.common.utils.RegexUtil;
import com.nebula.salary.common.utils.SecurityUtil;
import com.nebula.salary.model.dto.ForgotDto;
import com.nebula.salary.model.dto.LoginDto;
import com.nebula.salary.model.dto.RegisterDto;
import com.nebula.salary.model.dto.UserQueryDto;
import com.nebula.salary.model.pojo.Role;
import com.nebula.salary.model.pojo.User;
import com.nebula.salary.model.vo.LoginUser;
import com.nebula.salary.model.vo.PageResult;
import com.nebula.salary.model.vo.UserVo;
import com.nebula.salary.portal.mapper.RoleMapper;
import com.nebula.salary.portal.mapper.UserMapper;
import com.nebula.salary.portal.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author codex
 * @since 2022-01-24
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Resource
    private AuthenticationManager authenticationManager;
    
    @Resource
    private RedisUtil redisUtil;
    
    @Resource
    private RoleMapper roleMapper;
    
    @Override
    public String login(LoginDto loginDto) {
        if (Strings.isNullOrEmpty(loginDto.getUsername())) {
            throw new ErrorException(ReturnMessage.USERNAME_CAN_NOT_BE_EMPTY);
        }
        
        if (Strings.isNullOrEmpty(loginDto.getPassword())) {
            throw new ErrorException(ReturnMessage.PASSWORD_CAN_NOT_BE_EMPTY);
        }
        
        Authentication authentication = null;
        
        try {
            // 该方法会去调用UserDetailService的loadByUsername方法进行登录
            authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword()));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                throw new UserPasswordNotMatchException();
            } else {
                throw new CustomException(e.getMessage());
            }
        }
        
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        String token = jwtTokenUtil.generateToken(loginUser);
        
        // 更新用户登录时间
        User user = new User();
        user.setUserId(loginUser.getUser().getUserId());
        user.setLoginTime(LocalDateTime.now());
        baseMapper.updateById(user);
        return token;
    }
    
    @Override
    public ServerResponse<String> register(RegisterDto registerDto) {
        
        checkData(registerDto.getUsername(), registerDto.getPassword(), registerDto.getRealName(),
                registerDto.getPhone(), registerDto.getAge());
        
        int count = baseMapper.selectCount(new QueryWrapper<User>().eq(User.USERNAME, registerDto.getUsername()));
        if (count > 0) {
            throw new ErrorException(ReturnMessage.USERNAME_IS_EXIST);
        }
        
        try {
            User user = new User();
            BeanUtils.copyProperties(registerDto, user);
            user.setRoleId(Const.DEFAULT_ROLE);
            user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
            if (Strings.isNullOrEmpty(user.getGender())) {
                user.setGender(Const.GENDER_SECRET);
            }
            baseMapper.insert(user);
            return ServerResponse.createBySuccessMessage(ReturnMessage.REGISTER_SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            return ServerResponse.createByErrorMessage(ReturnMessage.REGISTER_ERROR);
        }
    }
    
    /**
     * 退出登录
     *
     * @param request request
     */
    @Override
    public void logout(HttpServletRequest request) {
        LoginUser loginUser = jwtTokenUtil.getLoginUser(request);
        if (loginUser != null) {
            jwtTokenUtil.delLoginUser(loginUser.getUuid());
        }
    }
    
    /**
     * 根据用户名查询用户信息，包括角色信息
     *
     * @param username 用户名
     * @return 用户信息，包括角色信息
     */
    @Override
    public User selectUserByUsername(String username) {
        return baseMapper.selectUserByUsername(username);
    }
    
    /**
     * 获取有权管理的用户列表
     *
     * @param dto 用户管理查询数据传输类
     * @return 用户列表
     */
    @Override
    public PageResult getUserList(UserQueryDto dto) {
        Page<User> page = new Page<>(dto.getPage(), dto.getLimit());
        IPage<User> pages = null;
        if (User.isAdmin(SecurityUtil.getUserId())) {
            // 超级管理员
            pages = baseMapper.selectAll(page, dto);
        } else {
            // 从缓存中获取当前用户的角色数据
            Role role = redisUtil
                    .getObject(Const.LOGIN_USER_KEY + SecurityUtil.getUsername() + "_" + SecurityUtil.getUserId());
            if (role == null) {
                role = baseMapper.selectUserByUsername(SecurityUtil.getUsername()).getRoles().get(0);
            }
            pages = baseMapper.selectUserList(page, dto, role.getRoleLevel());
        }
        
        PageResult pageResult = new PageResult();
        pageResult.setTotal(pages.getTotal());
        pageResult.setCount(pages.getRecords().size());
        pageResult.setList(pages.getRecords());
        
        return pageResult;
    }
    
    /**
     * 校验数据
     *
     * @param user 用户信息
     */
    @Override
    public void checkData(User user) {
        checkData(user.getUsername(), user.getPassword(), user.getRealName(), user.getPhone(), user.getAge());
        
        if (user.getRoleId() == null || user.getRoleId() <= 0) {
            throw new CustomException("请选择用户角色");
        }
        
        if (user.getRoleId() == 1001) {
            throw new CustomException("不能分配超级管理员权限");
        }
        
    }
    
    /**
     * 校验用户名是否唯一
     *
     * @param username 用户名
     * @return 结果
     */
    @Override
    public String checkUsernameUnique(String username) {
        Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq(User.USERNAME, username));
        return count > 0 ? Const.NOT_UNIQUE : Const.UNIQUE;
    }
    
    /**
     * 校验手机号码用户名是否唯一
     *
     * @param user 用户信息
     * @return 结果
     */
    @Override
    public String checkPhoneUnique(User user) {
        long userId = user.getUserId() == null ? -1L : user.getUserId();
        User info = baseMapper.selectOne(new QueryWrapper<User>().eq(User.PHONE, user.getPhone()));
        if (info != null && info.getUserId() != userId) {
            return Const.NOT_UNIQUE;
        }
        return Const.UNIQUE;
    }
    
    /**
     * 根据用户ID获取用户信息
     *
     * @param userId 用户ID
     * @return 用户信息
     */
    @Override
    public UserVo getInfoByUserId(Long userId) {
        UserVo userVo = new UserVo();
        
        // 验证当前用户是否有权限查询用户信息
        this.checkUserDataScope(userId);
        
        User user = baseMapper.selectById(userId);
        BeanUtils.copyProperties(user, userVo);
        
        return userVo;
    }
    
    /**
     * 验证是否有权限操作该用户
     *
     * @param userId 用户ID
     */
    @Override
    public void checkUserDataScope(Long userId) {
        Long loginUserId = SecurityUtil.getUserId();
        String message = "没有权限访问用户数据！";
        if (!User.isAdmin(SecurityUtil.getUserId())) {
            if (User.isAdmin(userId)) {
                // 拒绝非超级管理查询超级管理员信息
                throw new ErrorException(message);
            }
            
            if (!loginUserId.equals(userId)) {
                Role loginUserRole = redisUtil.getObject(Const.LOGIN_USER_KEY + SecurityUtil.getUsername() + "_" + loginUserId);
                if (loginUserRole == null) {
                    loginUserRole = roleMapper.selectRoleByUserId(loginUserId);
                }
                Role selectUserRole = roleMapper.selectRoleByUserId(userId);
                if (selectUserRole == null || loginUserRole.getRoleLevel() >= selectUserRole.getRoleLevel()) {
                    // 无权
                    throw new ErrorException(message);
                }
            }
        }
    }
    
    /**
     * 批量删除
     *
     * @param userIds 用户ID
     * @return 影响行数
     */
    @Override
    public int deleteUserByIds(Long[] userIds) {
        for (Long userId : userIds) {
            if (User.isAdmin(userId)) {
                throw new ErrorException("不允许操作超级管理员用户");
            }
            checkUserDataScope(userId);
        }
        return baseMapper.deleteBatchIds(Arrays.asList(userIds));
    }
    
    /**
     * 找回密码
     * 
     * @param forgotDto 找回密码数据传输类
     * @return 影响行数
     */
    @Override
	public int forgot(ForgotDto forgotDto) {
    	String encodePassword = SecurityUtil.encryptPassword(forgotDto.getPassword());
		return baseMapper.updatePasswordByUsername(forgotDto.getUsername(), encodePassword);
	}
    
    private void checkData(String username, String password, String realName, String phone, Integer age) {
        if (Strings.isNullOrEmpty(username)) {
            throw new ErrorException(ReturnMessage.USERNAME_CAN_NOT_BE_EMPTY);
        }
        
        if (!RegexUtil.isAccountLegal(username)) {
            throw new ErrorException(ReturnMessage.ILLEGAL_USERNAME);
        }
        
        if (Strings.isNullOrEmpty(password)) {
            throw new ErrorException(ReturnMessage.PASSWORD_CAN_NOT_BE_EMPTY);
        }
        
        if (password.length() < Const.PASSWORD_MIN_LENGTH || password.length() > Const.PASSWORD_MAX_LENGTH) {
            throw new ErrorException(ReturnMessage.ILLEGAL_PASSWORD);
        }
        
        if (Strings.isNullOrEmpty(realName)) {
            throw new ErrorException(ReturnMessage.NAME_CAN_NOT_BE_EMPTY);
        }
        
        if (!Strings.isNullOrEmpty(phone) && !RegexUtil.isPhoneLegal(phone)) {
            throw new ErrorException(ReturnMessage.ILLEGAL_PHONE);
        }
        
        if (age != null && (age < Const.MIN_AGE || age > Const.MAX_AGE)) {
            throw new ErrorException(ReturnMessage.ILLEGAL_AGE);
        }
    }
}
